package tuling.netty.io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @author RenGaoshuai
 * @date 2023/11/20 13:16
 * @description Nio多路复用器 多条线路复用一个线程
 * 酒吧吧台，谁想倒酒请举手
 * Selector.open();打开
 * 首先服务端先阻塞到Selector,并且关注连接事件
 * selector.select();等待事件发生
 * 判断事件类型进行处理，连接事件需要把客户端socketChannel注册的selector上。
 *
 * 最底层原理，是基于linux的三个命令操作的，具体的实现是操作系统监听网卡，如果发生事件，就把事件添加到一个rdlist集合中。后续我们只需要操作rdlist集合即可。
 * jdknio是Java1.4就提供的api
 * 最开始的实现是select模型，没有事件响应机制，即一致遍历所有注册到selector上的channel，大小有限制最多1024
 * 然后优化为poll模型，跟select模型没有太大区别，唯一的就是把带线啊哦1024限制去掉了
 * 到现在优化为epoll模型，基于操作系统的响应模型，减小了没有事件响应的循环。
 *
 * 比如，我现在有10万个客户端注册到selector上。
 * select模型只会注册1024个，然后循环这1024个客户端，看有没有事件发生。
 * poll模型注册全部，循环这些客户端，看有没有事件发生。
 * epoll模型注册全部，有事件发生，把这个channel放到rdlist集合中，然后去便利处理rdlist集合。
 *
 * 底层调用的都是linux的epoll方法
 * epoll_create 创建一个epoll对象
 * epoll_ctl epoll绑定事件
 * epoll_wait 等待epoll上绑定的事件发生
 * 注意：windows底层操作系统不支持epoll，他是调用windows的winsock2方法实现的（不开源）。
 *
 * java nio的代码还是很复杂的，非常不好用，因此后续才有了netty对java nio的封装。就相当于 mybatis对jdbc的封装。
 *
 *
 */
public class EventNioServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        serverSocketChannel.socket().bind(new InetSocketAddress(9000));
        serverSocketChannel.configureBlocking(false);

        //打开Selector 本质上是获取操作系统的一个epoll对象
        Selector selector = Selector.open();

        //把serverSocketChannel注册到Selector上，并且关注连接事件（OP_ACCEPT）
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务端启动成功");

        while (true) {
            //阻塞等待事件发生
            selector.select();

            //获取全部事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();

            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();

                // 如果是OP_ACCEPT事件，则进行连接获取和事件注册
                if (key.isAcceptable()) {
                    ServerSocketChannel serverSocket = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = serverSocket.accept();
                    socketChannel.configureBlocking(false);

                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    // 如果是OP_READ事件，则进行读取和打印
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                    int len = socketChannel.read(byteBuffer);

                    if (len > 0) {
                        System.out.println("接收到消息：" + new String(byteBuffer.array()));
                    } else if (len == -1) {
                        // 如果客户端断开连接，关闭Socket
                        System.out.println("客户端断开连接");
                        socketChannel.close();
                    }
                }

                iterator.remove();
            }

        }
    }
}
